// Integers are the "hello world!"" of FFI as they are generally much easier to
// pass across the boundary.

enum ErrorCode {
    Success,
    AdditionError,
}

impl ErrorCode {
    fn value(&self) -> u32 {
        match *self {
            ErrorCode::Success => 0,
            ErrorCode::AdditionError => 1,
        }
    }
}

#[repr(C)]
pub struct AdditionResult {
    result: u32,
    error_code: u32,
}

// If addition succeeds, return { result: a + b, error_code: 0 };
// otherwise, return { result: 0, error_code: 1 }.
#[no_mangle]
pub extern "C" fn addition(a: u32, b: u32) -> AdditionResult {
    let result = a.checked_add(b);

    if let Some(sum) = result {
        AdditionResult {
            result: sum,
            error_code: ErrorCode::Success.value(),
        }
    } else {
        AdditionResult {
            result: 0,
            error_code: ErrorCode::AdditionError.value(),
        }
    }
}

#[cfg(test)]
mod tests {
    use super::{addition, AdditionResult};

    fn assert_addition(a: u32, b: u32, expected_result: AdditionResult) {
        let result = addition(a, b);

        assert_eq!(expected_result.result, result.result);
        assert_eq!(expected_result.error_code, result.error_code);
    }

    #[test]
    fn zero_and_zero() {
        assert_addition(
            0,
            0,
            AdditionResult {
                result: 0,
                error_code: 0,
            },
        );
    }

    #[test]
    fn zero_and_positive() {
        assert_addition(
            0,
            1,
            AdditionResult {
                result: 1,
                error_code: 0,
            },
        );
    }

    #[test]
    fn zero_and_max() {
        assert_addition(
            0,
            std::u32::MAX,
            AdditionResult {
                result: std::u32::MAX,
                error_code: 0,
            },
        );
    }

    #[test]
    fn positive_and_zero() {
        assert_addition(
            1,
            0,
            AdditionResult {
                result: 1,
                error_code: 0,
            },
        );
    }

    #[test]
    fn positive_and_positive() {
        assert_addition(
            1,
            1,
            AdditionResult {
                result: 2,
                error_code: 0,
            },
        );
    }

    #[test]
    fn one_and_max_minus_one() {
        assert_addition(
            1,
            std::u32::MAX - 1,
            AdditionResult {
                result: std::u32::MAX,
                error_code: 0,
            },
        );
    }

    #[test]
    fn one_and_max() {
        assert_addition(
            1,
            std::u32::MAX,
            AdditionResult {
                result: 0,
                error_code: 1,
            },
        );
    }
}
